import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.serializers.FieldSerializer;
import com.esotericsoftware.minlog.Log;


public class KryoProxyMain {
	static Kryo kryo;
	
	public static byte[] serialize(Object o) throws IOException {
		ByteArrayOutputStream buffer = new ByteArrayOutputStream();
		Output out = new Output(buffer);
		kryo.writeClassAndObject(out, o);
		out.flush();
		out.close();
		return buffer.toByteArray();
	}
	
	private static Object deserialize(byte[] b) throws IOException, ClassNotFoundException {
		ByteArrayInputStream in = new ByteArrayInputStream(b);
		//ObjectInputStream instream = new HookedObjectInputStream(cld, in);
		Input instream = new Input(in);
		return kryo.readClassAndObject(instream);
	}
	/**
	 * @param args
	 * @throws IOException 
	 * @throws ClassNotFoundException 
	 * @throws SecurityException 
	 * @throws NoSuchMethodException 
	 * @throws InvocationTargetException 
	 * @throws IllegalArgumentException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
		kryo = new Kryo();
		Log.TRACE();
		
		//Try out 1 --> register InvocationHandler
		//Not very generic
		
//		FieldSerializer serializer = new FieldSerializer<Object>(kryo, InvocationHandler.class){
//			
//			@Override
//			public Object read (Kryo kryo, Input input, Class<Object> type) {
//				return Proxy.newProxyInstance(kryo.getClassLoader(), new Class[] {InterfaceA.class, InterfaceB.class}, new ProxyHandler(new ProxyTest()));
//
//			}
//
//		};
//		
//		
//		
//		kryo.register(InvocationHandler.class, serializer);
		
		//Try out 2 --> make more generic
		//https://github.com/magro/kryo-serializers/tree/master/src/main/java/de/javakaffee/kryoserializers
		
		class KryoProxySerializer extends Serializer<Object>{
			
			@Override
			public void write(Kryo kryo, Output output, Object object) {
				kryo.writeClassAndObject(output, ((InvocationHandler) Proxy.getInvocationHandler(object)));
		        kryo.writeObject( output, object.getClass().getInterfaces() );
			}

			@Override
			public Object read(Kryo kryo, Input input, Class<Object> type) {
				final InvocationHandler proxyHandler = (InvocationHandler) kryo.readClassAndObject(input);
		        final Class<?>[] interfaces = kryo.readObject(input, Class[].class);
		        return createProxy(kryo, proxyHandler, interfaces);
			}
			
			private Object createProxy(Kryo kryo, InvocationHandler proxyHandler, Class<?>[] interfaces) {
				return Proxy.newProxyInstance(kryo.getClassLoader(), interfaces, proxyHandler);
			}
			
		};

		kryo.register(InvocationHandler.class, new KryoProxySerializer());

		//Test 1 Proxy class
		System.out.println("TEST 1");
		
		ProxyTest testObject = new ProxyTest();
		
		System.out.println(testObject instanceof InterfaceMain);
		
		byte[] ser = serialize(testObject);
		Object res = deserialize(ser);
		
		System.out.println(res instanceof InterfaceMain);
		
		//Test 2 Dynamic Proxy class
		System.out.println("TEST 2");
		ClassLoader cld = Thread.currentThread().getContextClassLoader();
		Object testObject2 = Proxy.newProxyInstance(cld, new Class[] {InterfaceA.class, InterfaceB.class}, new ProxyHandler(new ProxyTest()));
		Class proxyClass = Proxy.getProxyClass(cld, new Class[] {InterfaceA.class, InterfaceB.class});
		
		System.out.println(testObject instanceof InterfaceMain);
		System.out.println(proxyClass.cast(testObject2).getClass().getMethod("getNumber").invoke(proxyClass.cast(testObject2))); //TODO dit is vuil
		System.out.println(((ProxyHandler) Proxy.getInvocationHandler(testObject2)).obj.getClass());
		
		Log.info("Starting ser");
		ser = serialize(testObject2);
		Log.info("Starting deser");
		res = deserialize(ser);
		
		
		System.out.println(res instanceof InterfaceMain);
		System.out.println(proxyClass.cast(res).getClass().getMethod("getNumber").invoke(proxyClass.cast(res)));
		
		
	}

}
